<?php
namespace Tlf\User;
trait Throttle {
public function clear_stale_throttles(){
$this->pdo->exec(
"DELETE FROM throttle WHERE expires_at < NOW(6)"
);
}
/**
* microsleep() for the amount of time remaining on the throttle
*/
public function throttle(string $key, string $actor){
$remaining = $this->throttle_remaining($key, $actor);
// var_dump($remaining);
if ($remaining < 0)return;
// exit;
usleep($remaining * 1000);
}
/**
* @param $key the action being throttled
* @param $actor ip address or other identifier for who's being throttled
*
* @return milliseconds remaining on the throttle or 0 if no throttle
*/
public function throttle_remaining(string $key, string $actor){
// i need to subtract NOW() from expires_at
// then convert that to milliseconds
// and that's the amount of time remaining on the throttle
//
$start = microtime(true);
$stmt = $this->pdo->prepare(
"SELECT TIMESTAMPDIFF(MICROSECOND, NOW(6), MAX(expires_at)) / 1000 as remaining FROM throttle t
WHERE `key` LIKE :key
AND actor LIKE :actor
AND expires_at > NOW(6)
"
);
$stmt->execute([
'key'=>$key,
'actor'=>$actor
]);
$mid = microtime(true);
$rows = $stmt->fetchAll();
$stmt->closeCursor();
unset($stmt);
if (count($rows)==0)return 0;
if (count($rows)>1){
throw new \Exception("I haven't yet handled the case of multiple throttles present in the db for the same key.");
}
// print_r($rows[0]);
$end = microtime(true);
// echo "\nFull:".(1000*($end - $start));
// echo "\nMid:".(1000*($end - $mid));
return (int)$rows[0]['remaining'];
}
public function add_throttle(string $key, string $actor, int $millis_from_now){
$seconds = ((double)$millis_from_now)/1000;
$stmt = $this->pdo->prepare(
" INSERT INTO throttle (`key`, actor, expires_at)
VALUES(:key, :actor,
TIMESTAMPADD(SECOND, :seconds, NOW(6))
) ;
"
);
$start = microtime(true);
$stmt->execute([
'key'=>$key,
'actor'=>$actor,
'seconds'=>$seconds
]);
$end = microtime(true);
// echo "\nInsert millis: ".(1000*($end - $start));
}
}